using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using CollectionExtensions.Extensions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OrangeProject.ReactiveList.Core.Client;
using OrangeProject.ReactiveList.Core.Communication;
using OrangeProject.ReactiveList.Core.Entities;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using QueueDeclareOk = RabbitMQ.Client.QueueDeclareOk;

namespace OrangeProject.ReactiveList.RabbitMQTransportCore
{
    public class RabbitMqTransportCore : ITransportCore
    {
        private const String SERVER_SIDE_ROUTING_KEY = "Server";
        private const String CLIENT_SIDE_ROUTING_KEY = "Client";

        private String _ExchangeName;
        private IModel _Channel;
        private WorkMode _workMode;
        private Action<DataBody> _receivedAction;
        private QueueDeclareOk _queue;

        private JsonSerializerSettings _serializerSettings = new JsonSerializerSettings
        {
            ContractResolver = new MyContractResolver(),
            Formatting = Formatting.Indented
        };

        public RabbitMqTransportCore(string exchangeName, IConnection connection, WorkMode workMode)
        {
            _ExchangeName = exchangeName;
            _workMode = workMode;

            _Channel = connection.CreateModel();
            _Channel.ExchangeDeclare(exchangeName, "direct", true, false);

            _queue = _Channel.QueueDeclare(durable: true, exclusive: true, autoDelete: true);
            _Channel.QueueBind(_queue.QueueName, exchangeName,
                _workMode == WorkMode.Server ? SERVER_SIDE_ROUTING_KEY : CLIENT_SIDE_ROUTING_KEY);

            var consumer = new EventingBasicConsumer(_Channel);
            _Channel.BasicConsume(_queue.QueueName, true, consumer);

            consumer.Received += (sender, args) =>
            {
                try
                {
                    _receivedAction(Deserialize(args.Body));
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                    throw;
                }
            };
        }

        public void SendState(DataBody data, SendTo to)
        {
            _Channel.BasicPublish(_ExchangeName,to == SendTo.Server ? SERVER_SIDE_ROUTING_KEY : CLIENT_SIDE_ROUTING_KEY, body: Serialize(data));
        }

        public void ReceiveState(Action<DataBody> receivedAction)
        {
            _receivedAction = receivedAction;
        }

        public DataContext DataContext { get; set; }

        public void Dispose()
        {
            _Channel.QueueDelete(_queue.QueueName);
            _Channel.Close();
            _Channel.Dispose();
        }

        private Byte[] Serialize(DataBody data)
        {
            var json = JsonConvert.SerializeObject(data, _serializerSettings);
            return Encoding.UTF8.GetBytes(json);
        }

        private DataBody Deserialize(Byte[] data)
        {
            DataBody dataBody = new DataBody
            {
                Data = new List<StateContainer<IEntity>>()
            };


            var json = Encoding.UTF8.GetString(data);
            JObject jobj = JObject.Parse(json);
            dataBody.Guid = jobj["Guid"].Value<String>();
            dataBody.Parameters = jobj["Parameters"].ToObject<Dictionary<String, Object>>();
            var jsonData = jobj["Data"].ToObject<JObject[]>();
            jsonData.ForEach(jsonElement =>
            {
                var name = jsonElement["Name"].Value<String>();
                var list = DataContext.Lists.SingleOrDefault(listItem => listItem.Name == name);
                if (list != null)
                {
                    var container = new StateContainer<IEntity>();
                    container.Name = name;

                    IEntity[] assign(IEnumerable<JObject> source)
                    {
                        var entities = new List<IEntity>();

                        source.ForEach(sourceItem =>
                        {
                            var entity = list.Creator();
                            var type = entity.GetType();

                            Dictionary<String, PropertyInfo> properties = new Dictionary<string, PropertyInfo>();
                            type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).ForEach(field => properties.Add(field.Name, field));
                            var jPorperties = sourceItem.Properties();
                            jPorperties.ForEach(propery =>
                            {
                                
                                if (properties.ContainsKey(propery.Name))
                                {
                                    var propOfObject = properties[propery.Name];
                                    var value = propery.Value.ToObject(propOfObject.PropertyType);
                                    propOfObject.SetValue(entity, value);
                                }
                            });
                            entities.Add(entity);
                        });
                        return entities.ToArray();
                    }

                    container.Created = assign(jsonElement["Created"].Values<JObject>());
                    container.Updated = assign(jsonElement["Updated"].Values<JObject>());
                    container.Deleted = assign(jsonElement["Deleted"].Values<JObject>());

                    dataBody.Data.Add(container);
                }
            });


            return dataBody;
        }
    }

    public enum WorkMode
    {
        Server,
        Client
    }
}